package com.wo.cloud.log.aspect;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.wo.cloud.core.util.IPUtil;
import com.wo.cloud.core.util.RequestHolder;
import com.wo.cloud.core.context.SpringContextHolder;
import com.wo.cloud.core.util.ThrowableUtil;
import com.wo.cloud.log.annotation.Log;
import com.wo.cloud.log.event.LogEvent;
import com.wo.cloud.core.entity.CommonLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author liry
 * @version 1.0
 * @date Created on 2021/4/29 21:22
 * Description: 日志拦截器
 */

@Slf4j
@Aspect
@Component
public class LogAspect {
    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    @Autowired
    private final ApplicationContext applicationContext;

    public LogAspect(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    // 定义切面
    @Pointcut("@annotation(com.wo.cloud.log.annotation.Log)")
    public void pointcut() {
    }


    /**
     * 配置环绕通知,使用 pointcut 切入点
     *
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object recordLog(ProceedingJoinPoint point) throws Throwable {

        // 初始化是否收集日志
        boolean isLog = false;

        // 打印执行时间
        long startTime = System.nanoTime();

        Object result = new Object();

        // 2. 获取请求类名称
        String className = point.getTarget().getClass().getCanonicalName();

        // 3. 获取注解里的 value 值
        // 3.1 （利用反射）获取方法
        Method method = resolveMethod(point);

        // 3.2 获取请求方法中参数值
        String methodName = method.getName();

        // 3.3 判断方法是否有Log注解
        Log annotation = method.getAnnotation(Log.class);
        if (ObjectUtil.isNotEmpty(annotation)) {
            isLog = true;
        }


        // 4. 获取请求中的相关信息
        //  获取 request 信息
        HttpServletRequest request = RequestHolder.getRequest();
        if (ObjectUtil.isEmpty(request)) {
            return point.proceed();
        }

        // 4.1 获取请求方式
        String requestMethod = request.getMethod();

        // 4.2 获取请求参数
        Object[] args = point.getArgs();
        String requestParam = getArgs(args, request);

        // 4.3 获取请求头参数
        Map<String, Object> headerMap = resolveHeaders(request);

        // 获取请求路由
        String requestURI = request.getRequestURI();

        // 4.4 计算请求执行时间

        // 获取IP和地区
        String ip = RequestHolder.getHttpServletRequestIpAddress();
        String region = IPUtil.getCityInfo(ip);

        //TODO：用户代理
//        logApi.setUserAgent(request.getHeader("user-agent"));

        long tookTime = 0L;
        try {
            result = point.proceed();
        } finally {
            // 获取请求执行时间
            tookTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);

            //　如果是登录请求，则不获取用户信息
            String userName = null;
            // TODO
//            if (!requestURI.contains("oauth") && !(requestURI.contains("code"))) {
//                //判断header是否存在，存在则获取用户名
//                if (StringUtils.isNotBlank(SecurityUtil.getHeaderToken(request))) {
//                    userName = SecurityUtil.getUsername(request).getAccount();
//                }
//            }

            if (isLog) {
                // 封装日志
                CommonLog commonLog = new CommonLog();
                commonLog.setIp(ip)
                        .setMethod(requestMethod)
                        .setUrl(requestURI)
                        .setParams(JSON.toJSONString(requestParam))
                        .setExecuteTime(tookTime)
                        .setLocation(StringUtils.isBlank(region) ? "本地" : region)
                        .setCreateBy(userName)
                        .setTitle(annotation.value())
                        .setOperation(String.valueOf(result))
                        .setMethodName(methodName)
                        .setClassName(className)
                        .setHeaders(JSONObject.toJSONString(headerMap));
                // 发布日志事件
                System.out.println("---" + commonLog);
                SpringContextHolder.publishEvent(new LogEvent(commonLog));
            }

        }
        return result;
    }

    /**
     * 配置异常通知: 发生异常时执行该方法
     *
     * @param point join point for advice
     * @param e     exception
     */
    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
    public void logAfterThrowing(JoinPoint point, Throwable e) throws NoSuchMethodException {
        // 打印执行时间
        long startTime = System.nanoTime();

        CommonLog commonLog = new CommonLog();

        // 获取IP和地区
        String ip = RequestHolder.getHttpServletRequestIpAddress();
        String region = IPUtil.getCityInfo(ip);


        //　获取request
        HttpServletRequest request = RequestHolder.getRequest();

        // 请求方法
        String method = request.getMethod();
        String url = request.getRequestURI();

        //　获取注解里的value值
        Method targetMethod = resolveMethod((ProceedingJoinPoint) point);
        Log logAnn = targetMethod.getAnnotation(Log.class);

        commonLog.setExecuteTime(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime))
                .setIp(ip)
                .setLocation(region)
                .setMethod(method)
                .setUrl(url)
//                .setTraceId(TraceUtil.getTraceId(request))
                .setType("2")
                .setTitle(logAnn.value())
                .setException(ThrowableUtil.getStackTrace(e));
        //设置MDC
//        TraceUtil.mdcTraceId(TraceUtil.getTraceId(request));
        // 发布事件
        applicationContext.publishEvent(new LogEvent(commonLog));
        log.info("Error Result: {}", commonLog);
    }

    // 获取方法
    private Method resolveMethod(ProceedingJoinPoint point) throws NoSuchMethodException {
        Class<?> aClass = point.getTarget().getClass();
        // 1.获取方法签名(通过此签名获取目标方法信息)
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        // 2.获取目标方法上的注解指定的操作名称
        // 2.1 获取方法名称
        String name = methodSignature.getName();
        // 2.2 获取方法参数
        Class[] parameterTypes = methodSignature.getParameterTypes();
        return aClass.getMethod(name, parameterTypes);
    }

    // 获取请求参数
    private String getArgs(Object[] args, HttpServletRequest request) {
        String result = StringPool.EMPTY;
        try {
            if (!MediaType.MULTIPART_FORM_DATA_VALUE.equals(request.getContentType())) {
                result = JSONObject.toJSONString(args);
            }
        } catch (Exception e) {
            try {
                result = Arrays.toString(args);
            } catch (Exception ex) {
                log.warn("解析参数异常", ex);
            }
        }
        return result;
    }

    // 获取请求头参数
    private Map<String, Object> resolveHeaders(HttpServletRequest request) {
        Map<String, Object> headerMap = new HashMap<>(16);

        // 获取请求头键值对
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = request.getHeader(headerName);
            headerMap.put(headerName, headerValue);
        }
        return headerMap;
    }


}
